package com.lzw.okgoutils.http;

import android.app.Application;
import android.content.Context;
import android.util.Log;

import com.lzw.okgoutils.http.otherutils.LogUtil;
import com.lzw.okgoutils.http.struct.StringFileMap;
import com.lzw.okgoutils.http.struct.StringHashMap;
import com.lzw.okgoutils.http.struct.StringMultiFileMap;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.cache.CacheMode;
import com.lzy.okgo.callback.FileCallback;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.cookie.CookieJarImpl;
import com.lzy.okgo.cookie.store.DBCookieStore;
import com.lzy.okgo.https.HttpsUtils;
import com.lzy.okgo.interceptor.HttpLoggingInterceptor;
import com.lzy.okgo.model.HttpHeaders;
import com.lzy.okgo.model.HttpParams;
import com.lzy.okgo.model.Progress;
import com.lzy.okgo.model.Response;
import com.lzy.okgo.request.PostRequest;
import com.lzy.okgo.request.Request;

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import okhttp3.Headers;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;

/**
 * 网络请求类，内部用来管理和封装OKgo
 * 外部使用时只需要在Application中调用一行代码进行初始化:
 * HttpClient.getInstance().init(this);
 * 具体使用:
 * HttpClient.getInstance().get();
 * HttpClient.getInstance().post();
 * HttpClient.getInstance().uploadFile();
 * HttpClient.getInstance().downloadFile();
 * HttpClient.getInstance().cancelRequest();
 * 同步方法：
 * HttpClient.getInstance().getSync();
 * HttpClient.getInstance().postSync();
 */
public class HttpClient {
    private static final String TAG = HttpClient.class.getSimpleName();
    private static HttpClient mInstance;
    private final static int CONNECT_TIME_OUT =  60 * 1000;
    private final static int READ_TIME_OUT =  60 * 1000;
    private final static int WRITE_TIME_OUT =  60 * 1000;
    private final static int RETRY_COUNT =  3;
    private HttpHeaders mCommonHeaders = null;
    private HttpParams mCommonParams = null;

    private HttpClient(){
    }

    public static HttpClient getInstance(){
        if(mInstance == null){
            mInstance = new HttpClient();
        }
        return mInstance;
    }

    public void init(Application context){
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //Log相关设置
        setLogUtil(builder);
        //超时相关设置
        setTimeOut(builder);
        //cokie设置
        setCookie(builder, context);
        //https设置
        setHttps(builder, context);
        //设置统一共用的header，根据需要设置
        setCommonHeades();
        //设置统一共用的请求参数，根据需要设置
        setCommonParams();
        //初始化OKGo
        OkGo.getInstance().init(context)                        //必须调用初始化
                .setOkHttpClient(builder.build())               //建议设置OkHttpClient，不设置将使用默认的
                .setCacheMode(CacheMode.NO_CACHE)               //全局统一缓存模式，默认不使用缓存，可以不传
                //.setCacheTime(CacheEntity.CACHE_NEVER_EXPIRE)   //全局统一缓存时间，默认永不过期，可以不传
                .setRetryCount(RETRY_COUNT)                     //全局统一超时重连次数，默认为三次，那么最差的情况会请求4次(一次原始请求，三次重连请求)，不需要可以设置为0
                .addCommonHeaders(mCommonHeaders)               //全局公共头
                .addCommonParams(mCommonParams);                //全局公共参数
    }

    public void setLogUtil(OkHttpClient.Builder builder){
        HttpLoggingInterceptor LogUtilgingInterceptor = new HttpLoggingInterceptor("OkGo");
        if(LogUtil.isLogEnable()){
            //Log打印级别，决定了Log显示的详细程度
            LogUtilgingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY);
            //Log颜色级别，决定了Log在控制台显示的颜色
            LogUtilgingInterceptor.setColorLevel(Level.INFO);
        }else{
            LogUtilgingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.NONE);
            LogUtilgingInterceptor.setColorLevel(Level.OFF);
        }
        builder.addInterceptor(LogUtilgingInterceptor);
    }

    public void setTimeOut(OkHttpClient.Builder builder) {
        builder.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS);
        builder.writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS);
        builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS);
    }

    public void setCookie(OkHttpClient.Builder builder, Context context) {
        //使用sp保持cookie，如果cookie不过期，则一直有效
        //builder.cookieJar(new CookieJarImpl(new SPCookieStore(context)));
        //使用数据库保持cookie，如果cookie不过期，则一直有效
        builder.cookieJar(new CookieJarImpl(new DBCookieStore(context)));
        //使用内存保持cookie，app退出后，cookie消失
        //builder.cookieJar(new CookieJarImpl(new MemoryCookieStore()));
        //builder.cookieJar(new CookiesManager(new PersistentCookieStore(context)));
    }

    public void setHttps(OkHttpClient.Builder builder, Context context) {
        //方法一：信任所有证书,不安全有风险
        HttpsUtils.SSLParams sslParams1 = HttpsUtils.getSslSocketFactory();
        //方法二：自定义信任规则，校验服务端证书
        //HttpsUtils.SSLParams sslParams2 = HttpsUtils.getSslSocketFactory(new SafeTrustManager());
        //方法三：使用预埋证书，校验服务端证书（自签名证书）
        //HttpsUtils.SSLParams sslParams3 = HttpsUtils.getSslSocketFactory(context.getResources().getAssets().open("srca.cer"));
        //方法四：使用bks证书和密码管理客户端证书（双向认证），使用预埋证书，校验服务端证书（自签名证书）
        //HttpsUtils.SSLParams sslParams4 = HttpsUtils.getSslSocketFactory(context.getResources().getAssets().open("xxx.bks"), "123456", context.getResources().getAssets().open("yyy.cer"));
        //builder.sslSocketFactory(sslParams1.sSLSocketFactory, sslParams1.trustManager);
        //配置https的域名匹配规则，详细看demo的初始化介绍，不需要就不要加入，使用不当会导致https握手失败
        //builder.hostnameVerifier(new SafeHostnameVerifier());
    }

    public void setCommonHeades(){
        /*HttpHeaders headers = new HttpHeaders();
        headers.put("commonHeaderKey1", "commonHeaderValue1");
        headers.put("commonHeaderKey2", "commonHeaderValue2");
        mCommonHeaders = headers;*/
    }

    public void setCommonParams(){
        /*
        //param支持中文,直接传,不要自己编码
        params.put("commonParamsKey2", "这里支持中文参数");
        */
//        HttpParams params = new HttpParams();
//        params.put("tqmobile",true);
//        mCommonParams = params;
    }

    /**
     * Get请求
     * @param request
     */
    public void get(final HttpRequest request){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        StringHashMap paramMaps = params.getAllParamStrs();
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "Get请求：" + url);
            Log.e(TAG, "请求参数：" + paramMaps.toString());
            Log.e(TAG, mCommonParams == null ? "" : mCommonParams.toString());
        }
        OkGo.<String>get(url)
                .tag(request.getTag())//.headers("header1", "headerValue1") //根据需要添加
                .params(paramMaps)
                .execute(new StringCallback() {


                    @Override
                    public void onSuccess(Response<String> response) {
                        handleResponse(response, request.getCallBack(), request.getTag());
                    }

                    @Override
                    public void onError(Response<String> response) {
                        HttpCallback callback = request.getCallBack();
                        if (callback != null) {
                            callback.onError("Error", request.getTag());
                        }
                        handleError(response);
                    }

                });
    }

    /**
     * Get同步请求, 外部需要在子线程中调用
     * @param request
     * @return String
     */
    public String getSync(final HttpRequest request){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        StringHashMap paramMaps = params.getAllParamStrs();
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "Get请求：" + url);
            Log.e(TAG, "请求参数：" + paramMaps.toString());
            Log.e(TAG, mCommonParams == null ? "" : mCommonParams.toString());
        }
        InputStreamReader inputStream = null;
        try {
            //okgo这种方式在接口返回大数据的json字符串时body为null
//            Call<String> call = OkGo.<String>get(url)
//                    .tag(request.getTag())
//                    .params(paramMaps)
//                    .converter(new StringConvert())
//                    .adapt();
//            Response<String> response = call.execute();
//            String res = response.body();

            StringBuilder urlBuilder = new StringBuilder(url);
            if (!paramMaps.isEmpty()) {
                urlBuilder.append("?");
                List<String> keyList = paramMaps.getKeyList();
                for (int i = 0; i < keyList.size(); i++) {
                    String key = keyList.get(i);
                    urlBuilder.append(key).append("=").append(paramMaps.get(key));
                    if (i < keyList.size()-1) {
                        urlBuilder.append("&");
                    }
                }
            }
            String realUrl = urlBuilder.toString();
            Log.e(TAG, "realUrl = " + realUrl);
            okhttp3.Request okrequest = new okhttp3.Request.Builder()
                    .url(realUrl)
                    .build();
            okhttp3.Response response = OkGo.getInstance()
                    .getOkHttpClient()
                    .newCall(okrequest)
                    .execute();
            okhttp3.ResponseBody body = response.body();
            if (body == null) {
                Log.e(TAG, "response.body() == null");
                Log.e(TAG, "result = null");
                return null;
            }
            //String res = body.string();
            String res = readFromInputStream(body.byteStream());
            Log.e(TAG, "result = " + res);
            body.close();
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "getSync Exception = " + e.toString());
        }
        return null;
    }

    /**
     * Post请求
     * @param request
     */
    public void post(final HttpRequest request){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        StringHashMap paramMaps = params.getAllParamStrs();
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "Post请求：" + url);
            Log.e(TAG, "请求参数：" + paramMaps.toString());
            Log.e(TAG, mCommonParams == null ? "" : mCommonParams.toString());
        }
        OkGo.<String>post(url)
                .tag(request.getTag())//.headers("header1", "headerValue1")
                .params(paramMaps)
                .isMultipart(true) //使用 multipart/form-data 表单上传
                .execute(new StringCallback() {

                    @Override
                    public void onSuccess(Response<String> response) {
                        handleResponse(response, request.getCallBack(), request.getTag());
                    }

                    @Override
                    public void onError(Response<String> response) {
                        HttpCallback callback = request.getCallBack();
                        if (callback != null) {
                            callback.onError("Error", request.getTag());
                        }
                        handleError(response);
                    }

                });
    }

    /**
     * Post同步请求, 外部需要在子线程中调用
     * @param request
     * @return
     */
    public String postSync(final HttpRequest request){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        StringHashMap paramMaps = params.getAllParamStrs();
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "Post请求："+url);
            Log.e(TAG, "请求参数：" + paramMaps.toString());
            Log.e(TAG, mCommonParams == null ? "" : mCommonParams.toString());
        }
        try {
//            Call<String> call = OkGo.<String>post(url)
//                    .tag(request.getTag())
//                    .params(paramMaps)
//                    .isMultipart(true)
//                    .converter(new StringConvert())
//                    .adapt();
//            Response<String> response = call.execute();
//            String res = response.body();

            MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
            if (!paramMaps.isEmpty()) {
                List<String> keyList = paramMaps.getKeyList();
                for (String key : keyList) {
                    builder.addFormDataPart(key, paramMaps.get(key));
                }
            }
            MultipartBody requestBody = builder.build();
            okhttp3.Request okrequest = new okhttp3.Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();
            okhttp3.Response response = OkGo.getInstance()
                    .getOkHttpClient()
                    .newCall(okrequest)
                    .execute();
            okhttp3.ResponseBody body = response.body();
            if (body == null) {
                Log.e(TAG, "response.body() == null");
                Log.e(TAG, "result = null");
                return null;
            }
            String res = readFromInputStream(body.byteStream());
            Log.e(TAG, "result = " + res);
            body.close();
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "postSync Exception = " + e.toString());
        }
        return null;
    }

    /**
     * 只上传1个文件
     * @param request
     */
    public void uploadFile(final HttpRequest request){
        uploadFile(request, false, false, false);
    }

    /**
     * 上传1个文件+多文本参数
     * @param request
     */
    public void uploadFileWithParams(HttpRequest request){
        uploadFile(request, true, false, false);
    }

    /**
     * 只上传多个文件，且一个文件对应一个key
     * @param request
     */
    public void uploadMultiFile(final HttpRequest request){
        uploadFile(request, false, true, false);
    }

    /**
     * 只上传多个文件，且多个文件对应同一个key
     * @param request
     */
    public void uploadMultiFileSameKey(final HttpRequest request){
        uploadFile(request, false, true, true);
    }

    /**
     * 上传多个文件+多文本参数，且一个文件对应一个key
     * @param request
     */
    public void uploadMultiFileWithParams(HttpRequest request){
        uploadFile(request, true, true, false);
    }

    /**
     * 上传多个文件+多文本参数，且多个文件对应同一个key
     * @param request
     */
    public void uploadMultiFileSameKeyWithParams(HttpRequest request){
        uploadFile(request, true, true, true);
    }

    /**
     * 根据是否多文件和是否携带文本参数进行分别处理
     * @param request     请求
     * @param isWithText  是否携带文本
     * @param isMultiFile 是否多文件
     * @param isMultiFileSameKey 多文件时是否同一个key
     */
    private void uploadFile(final HttpRequest request, boolean isWithText, boolean isMultiFile, boolean isMultiFileSameKey){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        Log.e(TAG, "uploadFile："+ url + "\n"+"请求参数：");
        Log.e(TAG, mCommonParams == null ? "" : mCommonParams.toString());
        List<String> fileKeyLists = new ArrayList<String>();
        List<File> fileLists = new ArrayList<File>();
        List<List<File>> fileLists2 = new ArrayList<List<File>>();

        if(isMultiFile && isMultiFileSameKey){
            StringMultiFileMap fileParams2 = params.getMultiFileParams();
            fileKeyLists.addAll(fileParams2.getKeyList());
            fileLists2.addAll(fileParams2.getRawFileList());
            if (fileLists2.size() == 0) {
                return;
            }
        }else{
            StringFileMap fileParams = params.getFileParams();
            fileKeyLists.addAll(fileParams.getKeyList());
            fileLists.addAll(fileParams.getRawFileList());
            if (fileLists.size() == 0) {
                return;
            }
        }

        PostRequest<String> postRequest = OkGo.<String>post(url)
                .tag(request.getTag())//.headers("header1", "headerValue1")
                .isMultipart(true); //使用 multipart/form-data 表单上传

        if (isWithText) {
            StringHashMap textParams = params.getAllParamStrs();
            postRequest = postRequest.params(textParams);//文本参数
            if (LogUtil.isLogEnable()) {
                Log.e(TAG, textParams.toString());
            }
        }
        if(isMultiFile){
            if(isMultiFileSameKey){
                for (int i = 0; i < fileKeyLists.size(); i++){
                    postRequest = postRequest.addFileParams(fileKeyLists.get(i), fileLists2.get(i));//文件
                }
                if (LogUtil.isLogEnable()) {
                    Log.e(TAG, params.getMultiFileParams().toString());
                }
            }else{
                for (int i = 0; i < fileLists.size(); i++){
                    postRequest = postRequest.params(fileKeyLists.get(i), fileLists.get(i));//文件
                }
                if (LogUtil.isLogEnable()) {
                    Log.e(TAG, params.getFileParams().toString());
                }
            }
        }else{
            postRequest = postRequest.params(fileKeyLists.get(0), fileLists.get(0));//文件
            if (LogUtil.isLogEnable()) {
                Log.e(TAG, params.getFileParams().toString());
            }
        }

        postRequest.execute(new StringCallback() {

                    @Override
                    public void onStart(Request<String, ? extends Request> okrequest) {
                        super.onStart(okrequest);
                        HttpCallback callback = request.getCallBack();
                        if (callback != null && callback instanceof HttpProgressCallback) {
                            ((HttpProgressCallback)callback).onStart("开始上传", request.getTag());
                        }
                    }

                    @Override
                    public void onSuccess(Response<String> response) {
                        handleResponse(response, request.getCallBack(), request.getTag());
                    }

                    @Override
                    public void onError(Response<String> response) {
                        HttpCallback callback = request.getCallBack();
                        if (callback != null) {
                            callback.onError("Error", request.getTag());
                        }
                        handleError(response);
                    }

                    @Override
                    public void uploadProgress(Progress progress) {
                        Log.e(TAG, "uploadProgress: " + progress);
                        HttpCallback callback = request.getCallBack();
                        if(callback != null && callback instanceof HttpProgressCallback){
                            ((HttpProgressCallback)callback).onProgress((int) (progress.fraction * 100), request.getTag());
                        }
                    }
                });
    }

    /**
     * 下载文件
     * @param request
     */
    public void downloadFile(final HttpRequest request){
        RequestParams params = request.getRequestParams();
        String url = request.getRequestUrl();
        StringHashMap paramMaps = params.getAllParamStrs();
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "downloadFile："+ url);
            Log.e(TAG, "请求参数：" + paramMaps.toString());
        }
        OkGo.<File>get(url)
                .tag(request.getTag())//.headers("header1", "headerValue1")
                .params(paramMaps)
                .execute(new FileCallback(params.getSaveDir(), params.getSaveName()) {

                    @Override
                    public void onStart(Request<File, ? extends Request> okrequest) {
                        HttpCallback callback = request.getCallBack();
                        if(callback != null && callback instanceof HttpProgressCallback){
                            ((HttpProgressCallback)callback).onStart("开始下载", request.getTag());
                        }
                    }

                    @Override
                    public void onSuccess(Response<File> response) {
                         if (response.body() != null) {
                             File file = (File) response.body();
                             HttpCallback callback = request.getCallBack();
                             if (callback != null) {
                                 callback.onSuccess(file.getPath(), request.getTag());
                             }
                             Log.e(TAG, file.getPath());
                        }
                    }

                    @Override
                    public void onError(Response<File> response) {
                       HttpCallback callback = request.getCallBack();
                        if (callback != null) {
                            callback.onError("Error", request.getTag());
                        }
                        handleError(response);
                    }

                    @Override
                    public void downloadProgress(Progress progress) {
                        if (LogUtil.isLogEnable()) {
                            Log.e(TAG, "downloadProgress: " + progress);
                        }
                        HttpCallback callback = request.getCallBack();
                        if(callback != null && callback instanceof HttpProgressCallback){
                            ((HttpProgressCallback)callback).onProgress((int) (progress.fraction * 100), request.getTag());
                        }
                    }
                });
    }

    /**
     * 在接口返回比较大量数据时必须用StringBuilder或StringBuffer循环读取
     */
    private String readFromInputStream(InputStream inputStream){
        if (inputStream == null) {
            return null;
        }
        try {
            StringBuilder sb = new StringBuilder();
            byte[] buff = new byte[1024];
            int readCount = 0;
            while ((readCount = inputStream.read(buff)) != -1) {
                sb.append(new String(buff, 0, readCount));
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "readFromInputStream "+e.toString());
        }
        return null;
    }

    protected <T> void handleResponse(Response<T> response, HttpCallback callback, Object tag) {
        StringBuilder sb;
        String result = null;
        if(callback != null){
            Log.e(TAG, "请求成功: ");
            T body = response.body();
            if (body == null) {
                Log.e(TAG, "result = null");
                callback.onError("Error", tag);
            } else {
                if (body instanceof String) {
                    result = (String) body;
                    Log.e(TAG, "result = "+result);
                    callback.onSuccess(result, tag);
                } else if (body instanceof List) {
                    sb = new StringBuilder();
                    List list = (List) body;
                    for (Object obj : list) {
                        sb.append(obj.toString()).append("\n");
                    }
                    result = sb.toString();
                    Log.e(TAG, "result ="+result);
                    callback.onSuccess(result, tag);
                } else if (body instanceof Set) {
                    sb = new StringBuilder();
                    Set set = (Set) body;
                    for (Object obj : set) {
                        sb.append(obj.toString()).append("\n");
                    }
                    result = sb.toString();
                    Log.e(TAG, "result ="+result);
                    callback.onSuccess(result, tag);
                } else if (body instanceof Map) {
                    sb = new StringBuilder();
                    Map map = (Map) body;
                    Set keySet = map.keySet();
                    for (Object key : keySet) {
                        sb.append(key.toString()).append(" ： ").append(map.get(key)).append("\n");
                    }
                    result = sb.toString();
                    Log.e(TAG, "result ="+result);
                    callback.onSuccess(result, tag);
                }else {
                    result = body.toString();
                    Log.e(TAG, "result ="+result);
                    callback.onSuccess(result, tag);
                }
            }
        }
        if (LogUtil.isLogEnable()) {
            printHttpLogInfo(response);
        }
    }

    protected <T> void handleError(Response<T> response) {
        if (LogUtil.isLogEnable()) {
            Log.e(TAG, "请求失败: ");
            T body = response.body();
            if (body != null && body instanceof String) {
                Log.e(TAG, "result ="+(String) body);
            }
            printHttpLogInfo(response);
        }
    }

    private <T> void printHttpLogInfo(Response<T> response){
        if (response == null) return;
        StringBuilder sb;
        okhttp3.Call call = response.getRawCall();
        if (call != null) {
            Headers requestHeadersString = call.request().headers();
            Set<String> requestNames = requestHeadersString.names();
            sb = new StringBuilder();
            sb.append("请求方式：").append(call.request().method()).append("\n").append("url：").append(call.request().url());
            Log.e(TAG, "requestState: "+sb.toString());
            sb = new StringBuilder();
            for (String name : requestNames) {
                sb.append(name).append(" ： ").append(requestHeadersString.get(name)).append("\n");
            }
            Log.e(TAG, "requestHeader: "+sb.toString());
        }else{
            Log.e(TAG, "requestState: --");
            Log.e(TAG, "requestHeader: --");
        }
        okhttp3.Response rawResponse = response.getRawResponse();
        if (rawResponse != null) {
            Headers responseHeadersString = rawResponse.headers();
            Set<String> names = responseHeadersString.names();
            sb = new StringBuilder();
            sb.append("url ： ").append(rawResponse.request().url()).append("\n\n");
            sb.append("stateCode ： ").append(rawResponse.code()).append("\n");
            for (String name : names) {
                sb.append(name).append(" ： ").append(responseHeadersString.get(name)).append("\n");
            }
            Log.e(TAG, "responseHeader: "+sb.toString());
        }else{
            Log.e(TAG, "responseHeader: --");
        }
    }

    /**
     * 取消指定tag的请求
     * @param tag
     */
    public void cancelRequest(Object tag){
        OkGo.getInstance().cancelTag(tag);
    }

    /**
     * 取消所有指定tag的请求
     * @param tags
     */
    public void cancelRequest(List<Integer> tags){
        for (Integer tag : tags){
            OkGo.getInstance().cancelTag(tag);
        }
    }

    /**
     * 取消全局所有的请求
     */
    public void cancelAllRequest(){
        OkGo.getInstance().cancelAll();
    }
}
